package com.cd2cd.util.mbg;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cd2cd.domain.CommValidate;
import com.cd2cd.domain.gen.CommValidateCriteria;
import com.cd2cd.mapper.CommValidateMapper;
import com.cd2cd.util.DataCache;
import com.cd2cd.util.SpringContextUtil;
import com.cd2cd.util.StringUtil;
import com.cd2cd.util.ThreadLocalUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.DefaultJavaFormatter;
import org.mybatis.generator.api.dom.DefaultXmlFormatter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.Element;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import static com.cd2cd.util.DataCache.CacheEnumKeys.*;


/**
 * mapper xml可扩展<br />
 * 自动生成domain, xml, mapper子类
 * @author leiwuluan
 */
public class MapperExtPlugin extends PluginAdapter {

	private Logger log = LoggerFactory.getLogger(MapperExtPlugin.class);

	private String javaClientGeneratorPackage;
	private String javaClientGeneratorGenPackage;
	private String sqlMapGeneratorPackage;
	private String sqlMapGeneratorGenPackage;
	private String javaModelGeneratorPackage;
	private String javaModelGeneratorGenPackage;
	
	private String SUPER_STR = "Super";

	private String ALIAS_STR = "Alias";

	@Override
	public boolean validate(List<String> warnings) {
		this.javaClientGeneratorPackage = context.getJavaClientGeneratorConfiguration().getTargetPackage();
		this.sqlMapGeneratorPackage = context.getSqlMapGeneratorConfiguration().getTargetPackage();
		this.javaModelGeneratorPackage = context.getJavaModelGeneratorConfiguration().getTargetPackage();
		
		// = = = 往上推一级 = = = 
		
		javaClientGeneratorGenPackage = javaClientGeneratorPackage + ".gen";
		sqlMapGeneratorGenPackage = sqlMapGeneratorPackage + ".gen";
		javaModelGeneratorGenPackage = javaModelGeneratorPackage + ".gen";
		
		return true;
	}
	
	/**
	 * domain
	 */
	 public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

		 String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();

		 FullyQualifiedJavaType type = topLevelClass.getType();
		 String shortName = type.getShortName();
		 FullyQualifiedJavaType newType = new FullyQualifiedJavaType(javaModelGeneratorGenPackage +"." + SUPER_STR + shortName);

		 try {

			Field field = topLevelClass.getClass().getSuperclass().getDeclaredField("type");
			field.setAccessible(true);
			field.set(topLevelClass, newType);

			 // 判断是否有表关联查询
			 JSONArray relationData = DataCache.getDataCache(TABLE_RELATION, tableName.toLowerCase());
			 if (relationData != null) {
				 // 添加继承
				 FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(javaModelGeneratorGenPackage +"." + shortName + ALIAS_STR);
				 topLevelClass.addImportedType(superInterface);
				 topLevelClass.setSuperClass(superInterface);
			 }

			 JSONArray annos = DataCache.getDataCache(DOMAIN_ANNOTATIONS, tableName.toLowerCase());
			// System.out.println(String.format("tableName=%s -- annos=%s",tableName,annos ));
			 if(annos != null){
			 	//添加注解
				 toDoAnnos(topLevelClass,annos,tableName);
			 }

		 } catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	 }

	public void toDoAnnos(TopLevelClass topLevelClass, JSONArray annos ,String tableName){
//		getValidAndClassPath();

		List<JSONObject> jsonObjects =annos.toJavaList(JSONObject.class);
		Map<String, Map<String,Set<String>>> map = jsonObjects
				.stream()
				.filter(Objects::nonNull)
				.collect(
						Collectors
								.toMap(item -> underlineToCamel(item.getString("columnName"),false), item -> new HashMap<String,Set<String>>(){{
									String c = item.getString("c");
									String u = item.getString("u");
									if(StringUtil.isNotEmpty(c)) put("c", new HashSet<String>(){{addAll(JSONArray.parseArray(c).toJavaList(String.class));}});
									if(StringUtil.isNotEmpty(u)) put("u", new HashSet<String>(){{addAll(JSONArray.parseArray(u).toJavaList(String.class));}});
								}})
				);

		String InsertClass = underlineToCamel(tableName.toLowerCase(),Boolean.TRUE)+"Insert";
		String UpdateClass = underlineToCamel(tableName.toLowerCase(),Boolean.TRUE)+"Update";
		InnerInterface iClass = new InnerInterface(new FullyQualifiedJavaType(javaModelGeneratorGenPackage +"." + InsertClass));
		InnerInterface mClass = new InnerInterface( new FullyQualifiedJavaType(javaModelGeneratorGenPackage +"." + UpdateClass));
		topLevelClass.addInterfaces(iClass);
		topLevelClass.addInterfaces(mClass);

		for(org.mybatis.generator.api.dom.java.Field field : topLevelClass.getFields())
		{
			String fieldName = field.getName();
			Set<String> validSet = new HashSet<>();
			Map<String,Set<String> > validGroupsMap =new HashMap<>();
			if(map.containsKey(fieldName)){
				Map<String, Set<String>> columnValidMap = map.get(fieldName);
				boolean containsKeyC = columnValidMap.containsKey("c");
				boolean containsKeyU = columnValidMap.containsKey("u");

				if(containsKeyC) validSet.addAll(columnValidMap.get("c"));
				if(containsKeyU) validSet.addAll(columnValidMap.get("u"));

				for(String valid:validSet){
					if( containsKeyC && containsKeyU ){
						validGroupsMap.put(valid,new HashSet<String>(){{add(InsertClass);add(UpdateClass);}});
					}
					else{
						if(containsKeyC){
							validGroupsMap.put(valid,new HashSet<String>(){{add(InsertClass);}});
						}
						else{
							validGroupsMap.put(valid,new HashSet<String>(){{add(UpdateClass);}});
						}
					}
				}
			}

			for(Map.Entry<String, Set<String>> entry :validGroupsMap.entrySet()){
				String key = entry.getKey();
				Set<String> value = entry.getValue();
				String vName = key.substring(0, key.indexOf("("));
				Object vPath = DataCache.getDataCache(VALID_MAP, vName);

				if(vPath != null){
					String annotation;
					if(value.size() == 0){
						annotation = "@"+vName;
					}else{
						annotation = getFieldValidAnnotation(key, value);
					}
					field.addAnnotation(annotation);
					topLevelClass.addImportedType( vPath.toString());
				}

			}
		}


	}

	protected String getFieldValidAnnotation(String annotation, Set<String> groupSet) {
		Set<String> gs = new HashSet<>();
		for (String g : groupSet) {
			gs.add(g + ".class");
		}

		boolean hasValue = !annotation.endsWith("()");
		StringBuilder sb = new StringBuilder("@");
		sb.append(annotation.substring(0, annotation.indexOf("(") + 1));
		sb.append("groups={");
		sb.append(Strings.join(gs, ','));
		sb.append("}");
		if (hasValue) sb.append(", ");
		sb.append(annotation.substring(annotation.indexOf("(") + 1));
		return sb.toString();
	}

	// 下划线格式字符串转换为驼峰格式字符串  firstCapitalize: 首字母是否大写
	public static String underlineToCamel(String param,Boolean firstCapitalize) {
		if (param == null || "".equals(param.trim())) {
			return "";
		}
		int len = param.length();
		StringBuilder sb = new StringBuilder(len);
		for (int i = 0; i < len; i++) {
			char c = param.charAt(i);

			if (c == '_' && i != 0) {
				if (++i < len) {
					sb.append(Character.toUpperCase(param.charAt(i)));
				}
			} else {
				if (i == 0 && firstCapitalize){
					sb.append(Character.toUpperCase(param.charAt(i)));
				}else {
					sb.append(c);
				}

			}
		}
		return sb.toString();
	}

	private void addField(TopLevelClass topLevelClass, IntrospectedTable introspectedTable, String name) {


		CommentGenerator commentGenerator = context.getCommentGenerator();
		org.mybatis.generator.api.dom.java.Field field = new org.mybatis.generator.api.dom.java.Field();
		field.setVisibility(JavaVisibility.PRIVATE);
		field.setType(new FullyQualifiedJavaType("String"));
		field.setName(name);
		commentGenerator.addFieldComment(field, introspectedTable);
		topLevelClass.addField(field);

		char c = name.charAt(0);
		String camel = Character.toUpperCase(c) + name.substring(1);
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setName("set" + camel);
		method.addParameter(new Parameter(new FullyQualifiedJavaType("String"), name));


		method.addBodyLine("this." + name + "=" + name + ";");
		commentGenerator.addGeneralMethodComment(method, introspectedTable);
		topLevelClass.addMethod(method);
		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(new FullyQualifiedJavaType("String"));
		method.setName("get" + camel);

		method.addBodyLine("return " + name + ";");

		commentGenerator.addGeneralMethodComment(method, introspectedTable);
		topLevelClass.addMethod(method);
	}


	 /**
	  * 查询类
	  */
	public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
		
		FullyQualifiedJavaType type = topLevelClass.getType();
		String packageStr = type.getPackageName() + ".gen." + type.getShortName();
		FullyQualifiedJavaType newType = new FullyQualifiedJavaType(packageStr);
		try {
			Field field = topLevelClass.getClass().getSuperclass().getDeclaredField("type");
			field.setAccessible(true);
			field.set(topLevelClass, newType);
			
			String exampleType = introspectedTable.getExampleType();
			int lastIndex = exampleType.lastIndexOf(".");
			exampleType = exampleType.substring(0, lastIndex) + ".gen"+ exampleType.substring(lastIndex, exampleType.length());
			introspectedTable.setExampleType(exampleType);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
    }
	
	/**
	 * Mapper
	 */
	@Override
	public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
		
		FullyQualifiedJavaType type = interfaze.getType();
		FullyQualifiedJavaType newType = new FullyQualifiedJavaType(javaClientGeneratorGenPackage +"." + SUPER_STR + type.getShortName());
		Constants.setObjVal(interfaze, "type", newType);

		// extends BaseMapper
		String proBasePkg = ThreadLocalUtil.getProjectBasePkg();
		log.info("proBasePkg={}", proBasePkg);
		if(StringUtils.isNotBlank(proBasePkg)) {
			String baseMapperType = proBasePkg + ".curd.mapper.BaseMapper";
			FullyQualifiedJavaType baseMapper = new FullyQualifiedJavaType(baseMapperType);
			interfaze.addSuperInterface(baseMapper);
			interfaze.addImportedType(baseMapper);
		}
		return true;
	}
	
	@Override
	public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
		
		Constants.setObjVal(sqlMap, "targetPackage", sqlMapGeneratorGenPackage);
		Constants.setObjVal(sqlMap, "fileName", SUPER_STR + sqlMap.getFileName());
		
		// 设置 xml mapper namespace值
		Document document = Constants.getObjVal(sqlMap, "document");
		
		XmlElement rootElement = document.getRootElement();
		Attribute attribute = rootElement.getAttributes().get(0);
		
		FullyQualifiedJavaType daoType = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
		String genMapperPath = sqlMapGeneratorGenPackage +"."+ SUPER_STR + daoType.getShortName();
		
		Constants.setObjVal(attribute, "value", genMapperPath);
		
		context.getSqlMapGeneratorConfiguration().setTargetPackage(sqlMapGeneratorGenPackage);
		
		Constants.setObjVal(sqlMap, "isMergeable", false); // xml每次生成复盖
		
        return true;
    }
	
	/**
	 * 返回生成子类的文件
	 */
	@Override
	public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {

		String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();

		FullyQualifiedJavaType daoType = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
		String fileName = daoType.getFullyQualifiedName();
		String project = context.getJavaClientGeneratorConfiguration().getTargetProject();
		
		List<GeneratedJavaFile> files = new ArrayList<GeneratedJavaFile>();
		
		
		// - - - 自动生成Mapper子类 - - -
		String extFilePath = (project + fileName) .replaceAll("\\.", "/") + ".java";
		if( ! new File(extFilePath).exists()) {
			Interface childInterface = new Interface(daoType);
			childInterface.setVisibility(JavaVisibility.PUBLIC);
			FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(javaClientGeneratorGenPackage +"."+ SUPER_STR + daoType.getShortName());
			childInterface.addImportedType(superInterface);
			childInterface.addSuperInterface(superInterface);
			
			// @Mapper
			FullyQualifiedJavaType mapperAnType = new FullyQualifiedJavaType("org.apache.ibatis.annotations.Mapper");
			childInterface.addImportedType(mapperAnType);
			childInterface.addAnnotation("@Mapper");
			
			JavaFormatter javaFormatter = new DefaultJavaFormatter();
			GeneratedJavaFile file = new GeneratedJavaFile(childInterface, project, javaFormatter);
			files.add(file);
		}
		
		// - - - 自动生成BaseModel子类 - - -
		FullyQualifiedJavaType baseType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
		fileName = baseType.getFullyQualifiedName();
		project = context.getJavaModelGeneratorConfiguration().getTargetProject();
		extFilePath = (project + fileName) .replaceAll("\\.", "/") + ".java";
		if( ! new File(extFilePath).exists()) {
			TopLevelClass model = new TopLevelClass(baseType);
			model.setVisibility(JavaVisibility.PUBLIC);
			FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(javaModelGeneratorGenPackage +"."+ SUPER_STR + baseType.getShortName());
			model.addImportedType(superInterface);
			model.setSuperClass(superInterface);
			
			JavaFormatter javaFormatter = new DefaultJavaFormatter();
			GeneratedJavaFile file = new GeneratedJavaFile(model, project, javaFormatter);
			
			// - - - - - 序列化接口 - - - - - 
			FullyQualifiedJavaType serializable = new FullyQualifiedJavaType("java.io.Serializable");
			model.addImportedType(serializable);
			model.addSuperInterface(serializable);

            org.mybatis.generator.api.dom.java.Field field = new org.mybatis.generator.api.dom.java.Field();
            field.setFinal(true);
            field.setInitializationString("1L"); //$NON-NLS-1$
            field.setName("serialVersionUID"); //$NON-NLS-1$
            field.setStatic(true);
            field.setType(new FullyQualifiedJavaType("long")); //$NON-NLS-1$
            field.setVisibility(JavaVisibility.PRIVATE);
            context.getCommentGenerator().addFieldComment(field, introspectedTable);

            model.addField(field);
            
			files.add(file);
		}


		// todo 生成表关联文件 判断是否有表关联查询 - 有多线程问题，建议存入ThreadLocal
		JSONArray relationData = DataCache.getDataCache(TABLE_RELATION, tableName.toLowerCase());

		if (relationData != null) {
			// 生成文件
			FullyQualifiedJavaType superInterface = new FullyQualifiedJavaType(javaModelGeneratorGenPackage + "." + baseType.getShortName() + ALIAS_STR);

			TopLevelClass aliasClass = new TopLevelClass(superInterface);

			FullyQualifiedJavaType serializable = new FullyQualifiedJavaType("java.io.Serializable");

			aliasClass.setVisibility(JavaVisibility.PUBLIC);
			aliasClass.addImportedType(serializable);
			aliasClass.addSuperInterface(serializable);

			// 添加别名
			for (Object data : relationData) {
				JSONObject json = JSONObject.parseObject(data.toString());
				JSONObject rJson = json.getJSONObject("json");
				JSONArray relationArr = rJson.getJSONArray("relationArr");

				for (Object relationO : relationArr) {
					JSONObject relationJson = JSONObject.parseObject(relationO.toString());
					String alias = relationJson.getString("alias"); //别名
					addField(aliasClass,introspectedTable,alias);
				}
			}

			JavaFormatter javaFormatter = new DefaultJavaFormatter();
			GeneratedJavaFile file = new GeneratedJavaFile(aliasClass, project, javaFormatter);
			files.add(file);
		}

		return files;
	}
	
	@Override
	public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles(
			IntrospectedTable introspectedTable) {
		
		
		String fileName = introspectedTable.getMyBatis3XmlMapperFileName();
		String targetPackage = sqlMapGeneratorPackage;
		String targetProject = context.getSqlMapGeneratorConfiguration().getTargetProject();
		
		// 拷贝mybatis文件
		String xmlMapperPath = (targetProject + targetPackage) .replaceAll("\\.", "/") + "/";
		
		// 自定义文件存在 不自动再生成
		String extFilePath = xmlMapperPath + fileName;
		if( ! new File(extFilePath).exists()) {
			// - - - 自动生成xmlMapper子文件 - - -
			List<GeneratedXmlFile> files = new ArrayList<GeneratedXmlFile>();
			FullyQualifiedJavaType daoType = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
			XmlElement rootElement = new XmlElement("mapper");
			rootElement.addAttribute(new Attribute("namespace", daoType.getFullyQualifiedName()));
			
			Element element = new TextElement("<!-- 自定义sql -->\n\n");
			rootElement.addElement(element);
			
			// 加入自定义文档头 支持extends
			Document document = new Document("-//mybatis.org//DTD Mapper 3.0//EN", "http://mybatis.org/dtd/mybatis-3-mapper.dtd");
			document.setRootElement(rootElement);
			
			XmlFormatter xmlFormatter = new DefaultXmlFormatter();
			GeneratedXmlFile generatedXmlFile = new GeneratedXmlFile(document, fileName, targetPackage, targetProject, false, xmlFormatter);
			
			files.add(generatedXmlFile);
			
			return files;
		}
		
		
		return null;
	}

	public static void main(String[] args) {
		String tableName = "SYS_AUTH";
		System.out.println(underlineToCamel(tableName,Boolean.TRUE));
	}

}
